package main

import (
	"context"
	"fmt"
	"github.com/frankrap/huobi-api/hbdm"
	"github.com/robfig/cron"
	"log"
	"sync"
	"time"
)

type AverageKline struct {
	FiveAverage   float64 // 5 个15分钟均线值
	TenAverage    float64 // 10 个15分钟均线值
	ThirtyAverage float64 // 30 个15分钟均线值
}

type MACD struct {
	LastUpdate int64
	List       map[int64]struct {
		BuyAmount  float64
		SellAmount float64
		Gap        float64
	}
}

type Trade struct {
	Average            *AverageKline
	client             *hbdm.Client
	BuyRatio           float64
	SellRatio          float64
	RecommendBuyPrice  float64
	RecommendBuyAmount float64 // 推荐购买数量
	RecommendSellPrice float64
	ForceSellPrice     float64
	ForceBuyPrice      float64
	RecommendDirection int              // 0 买 1卖
	Account            hbdm.AccountInfo // 0 买 1卖
	BuyPosition        hbdm.Position    //
	SellPosition       hbdm.Position    //
	UnSellCount        uint             //
	UnBuyCount         uint             //
	conf               *Config          //
	forceSell          int              // 强势下跌信号 防止山顶
	forceBuy           int              // 强势底部回调信号 防止踏空
	optLock            sync.Mutex
	MaxAverage         float64
	MinAverage         float64
	ATR                float64 // 计算ATR
	RecommendGushu     float64 // 推荐仓位股数
	RecommendBalance   float64 // 推荐仓位金额
	ZiSunBalance       float64 // 止损
	TopPrice           float64 // 顶部价格
	CurrentPrice       float64 // 当前价格
	BottomPrice        float64 // 底部价格
	MACD               MACD    // 最新交易数据 保留最近 1小时 即
	ClosePrices        []float64
	HourClosePrices    []float64
	NeedForceSell      bool // 平空单
	NeedForceBuy       bool // 平多单
	Klines             []hbdm.SwapKLine
	Crons              *cron.Cron // 定时任务
	LastBuyAt          int64      // 定时任务
	LastSellAt         int64      // 定时任务
}

func NewTrade(client *hbdm.Client, config *Config) *Trade {
	t := &Trade{}
	t.client = client
	t.conf = config
	t.Account = hbdm.AccountInfo{}
	t.BuyPosition = hbdm.Position{}
	t.SellPosition = hbdm.Position{}
	t.Average = &AverageKline{
		FiveAverage:   0,
		TenAverage:    0,
		ThirtyAverage: 0,
	}
	t.ClosePrices = make([]float64, 0)
	t.HourClosePrices = make([]float64, 0)
	t.Klines = make([]hbdm.SwapKLine, 0)
	t.Crons = cron.New()
	return t
}

func (this *Trade) IsDanBian() bool {
	if this.forceSell == 1 || this.forceBuy == 1 {
		return true
	}
	return false
}

// 计算15分钟 均线
func (this *Trade) CronCalcKline(symbol string) {
	err := this.Crons.AddFunc("* * * * *", func() {
		this.RecommendDirection = -1
		// 每15分钟快结束时 预测下一个15分钟走势
		klineData, err := this.client.GetCoinKLine(symbol, "5min", 350, int64(0), int64(0))
		if err != nil {
			log.Println("请求出错", err)
			return
		}
		fives := this.SimpleAverage(klineData.Data)
		this.Klines = klineData.Data
		this.Macd(fives)
	})
	if err != nil {
		log.Println("kline 定时失败", err)
		return
	}
}

// 计算ATR
func (this *Trade) CronATR(wg *sync.WaitGroup, ctx context.Context, symbol string) {
	size := 60
	klineData, err := this.client.GetCoinKLine(symbol, "60min", size, int64(0), int64(0))
	if err != nil {
		log.Println("请求出错", err)
	} else {
		this.calc_atr(klineData.Data)
	}
	defer wg.Done()
	t := time.NewTicker(120 * time.Second)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			log.Println("退出计算ATR均线")
			return
		case <-t.C:
			// 15 分钟
			klineData, err := this.client.GetCoinKLine(symbol, "60min", size, int64(0), int64(0))
			if err != nil {
				log.Println("请求出错", err)
				continue
			}
			this.calc_atr(klineData.Data)
		}
	}
}

// 计算 持仓比
func (this *Trade) CronCalcRatio(wg *sync.WaitGroup, ctx context.Context, symbol string) {
	defer wg.Done()
	t := time.NewTicker(1 * time.Minute)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			log.Println("退出请求持仓比")
			return
		case <-t.C:
			// 1 分钟
			coinRatio, err := this.client.GetCoinRatio(symbol, "1day")
			if err != nil {
				log.Println("请求出错", err)
				continue
			}
			l := len(coinRatio.Data.List)
			this.BuyRatio = coinRatio.Data.List[l-1].BuyRatio
			this.SellRatio = coinRatio.Data.List[l-1].SellRatio
			log.Println("多仓的总持仓量占比", this.BuyRatio)
			log.Println("空仓的总持仓量占比", this.SellRatio)
		}
	}
}

// 计算 深度
func (this *Trade) CronCalcDept(wg *sync.WaitGroup, ctx context.Context, symbol string) {
	defer wg.Done()
	t := time.NewTicker(10 * time.Second)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			log.Println("退出请求深度")
			return
		case <-t.C:
			// 深度
			marketDept, err := this.client.GetCoinMarketDepth(symbol, "step9")
			if err != nil {
				log.Println("请求出错", err)
				continue
			}
			if len(marketDept.Tick.Asks) < 1 {
				log.Println("请求出错", marketDept.Tick.Asks)
				continue
			}
			this.CurrentPrice = marketDept.Tick.Asks[0][0]
			this.RecommendSellPrice = marketDept.Tick.Asks[0][0]
			this.RecommendBuyPrice = marketDept.Tick.Bids[0][0]
		}
	}
}

// 监控账户盈利情况
func (this *Trade) CronAccount(wg *sync.WaitGroup, ctx context.Context, symbol string) {
	defer wg.Done()
	t := time.NewTicker(5 * time.Second)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			log.Println("退出请求账户信息")
			return
		case <-t.C:
			// 账户详情
			accountInfo, err := this.client.GetSwapAccountInfo(symbol)
			if err != nil {
				log.Println("请求出错", err)
				continue
			}
			for _, v := range accountInfo.Data {
				if v.ContractCode == symbol {
					this.Account = v
					break
				}
			}
			this.RecommendGushu = this.Account.MarginAvailable * this.conf.Rate / this.ATR
			// 1张 = 100 U
			// 下单总金额
			// if this.CurrentPrice > 0 {
			// 	volume := int64(this.RecommendGushu * this.CurrentPrice / 100)
			// 	this.conf.Volume = float64(volume)
			// }
			log.Println("[", this.Account.ContractCode, "]",
				"账户权益", this.Account.MarginAvailable,
				"ATR", this.ATR,
				"未实现盈亏", this.Account.ProfitUnreal,
				"账户推荐股数", fmt.Sprintf("%.4f", this.RecommendGushu),
				"账户推荐张数", this.conf.Volume,
			)
		}
	}
}

// 平单策略
func (this *Trade) CloseOrder(symbol string) {
	canBuyClose := false
	canSellClose := false
	// 判断当前 是否需要平仓情况
	// 2. 判断macd 走势是否 开始反向走
	// 每15分钟快结束时 预测下一个15分钟走势 看是否反转 反转出单
	klineData, err := this.client.GetCoinKLine(symbol, "5min", 200, int64(0), int64(0))
	if err != nil {
		log.Println("请求出错", err)
		return
	}
	canBuyClose, canSellClose = this.MacdClose(klineData.Data)

	// 2. 判断macd 走势是否 开始反向走
	if !canSellClose && !canBuyClose {
		// 1. 盈利达到目标 2刀
		if this.BuyPosition.ProfitUnreal >= this.ATR/this.conf.Profit {
			canBuyClose = true
		}
		if this.SellPosition.ProfitUnreal >= this.ATR/this.conf.Profit {
			canSellClose = true
		}
	}
	if this.BuyPosition.Volume > 0 && canBuyClose { // 如果下跌单边 就及时出掉当前 多单 亏损单
		// 闪电平仓
		_, err := this.client.SwapLightOrder(symbol,
			symbol, this.BuyPosition.Volume, "sell")
		if err != nil {
			log.Println("闪电平仓 平多单", err)
			return
		}
		this.NeedForceBuy = false
		this.BuyPosition = hbdm.Position{}
	}
	// 平仓策略
	if this.SellPosition.Volume > 0 && canSellClose { // 如果上涨单边 就及时出掉当前 空单 亏损单
		// 闪电平仓
		_, err := this.client.SwapLightOrder(symbol,
			symbol, this.SellPosition.Volume, "buy")
		if err != nil {
			log.Println("闪电平仓 平空单", err)
			return
		}
		this.NeedForceSell = false
		this.SellPosition = hbdm.Position{}
	}
}

// 监控账户盈利情况
func (this *Trade) CronPosition(wg *sync.WaitGroup, ctx context.Context, symbol string) {
	defer wg.Done()
	t := time.NewTicker(2 * time.Second)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			log.Println("退出请求持仓信息")
			return
		case <-t.C:
			// 账户详情
			positionInfo, err := this.client.GetSwapPositionInfo(symbol)
			if err != nil {
				log.Println("请求出错", err)
				continue
			}
			this.BuyPosition = hbdm.Position{}
			this.SellPosition = hbdm.Position{}
			if len(positionInfo.Data) < 1 {
				this.NeedForceSell = false
				this.NeedForceBuy = false
				log.Println("暂无持仓数据")
				continue
			}
			for _, v := range positionInfo.Data {
				if v.ContractCode != symbol {
					continue
				}
				if v.Direction == "buy" {
					this.BuyPosition = v
					log.Println("[当前买单盈利] ", v.ContractCode, v.Profit, "数量", v.Volume,
						"开仓价", v.CostOpen, "止盈价", v.CostOpen+this.ATR/this.conf.Profit,
						"止损价", v.CostOpen-this.ATR*this.conf.Profit, "当前价", this.CurrentPrice)
				} else {
					this.SellPosition = v
					log.Println("[当前空单盈利] ", v.ContractCode, v.Profit, "数量", v.Volume,
						"开仓价", v.CostOpen, "止盈价", v.CostOpen-this.ATR/this.conf.Profit,
						"止损价", v.CostOpen+this.ATR*this.conf.Profit, "当前价", this.CurrentPrice)
				}
			}
			this.CloseOrder(symbol)
		}
	}
}

func (this *Trade) HandleUnOrders(symbol string, orders []hbdm.Order) {
	this.optLock.Lock()
	defer this.optLock.Unlock()
	for _, v := range orders {
		if v.ContractCode != symbol {
			continue
		}
		if v.Direction == "buy" {
			this.UnBuyCount++
		} else {
			this.UnSellCount++
		}
		if v.Offset == "close" {
			// 止盈单
			continue
		}
		v.CreatedAt /= 1000
		log.Println("现在时间", time.Now().Unix(), "委托订单时间", v.CreatedAt)
		// 15分钟未成交 则进行取消订单
		// 平均线看的15分钟 挂5分钟单
		if time.Now().Unix()-v.CreatedAt >= 30 ||
			(v.Direction == "buy" && this.RecommendDirection == 1) ||
			(v.Direction == "sell" && this.RecommendDirection == 0) {
			or, err := this.client.SwapCancel(symbol, v.OrderID, 0)
			if err != nil {
				log.Println("取消订单", err)
				continue
			}
			if v.Direction == "buy" {
				this.UnBuyCount--
			} else {
				this.UnSellCount--
			}
			log.Println("取消订单", or.ErrMsg, or.ErrCode)
		}
	}
}

// 监控账户订单情况
func (this *Trade) CronOrders(wg *sync.WaitGroup, ctx context.Context, symbol string) {
	orders, err := this.client.GetSwapOpenOrders(symbol, 1, 50)
	if err != nil {
		log.Println("订单请求出错", err)
	}
	defer wg.Done()
	t := time.NewTicker(10 * time.Second)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			log.Println("退出请求委托列表")
			return
		case <-t.C:
			// 账户详情
			orders, err = this.client.GetSwapOpenOrders(symbol, 1, 50)
			if err != nil {
				log.Println("订单请求出错", err)
				continue
			}
			if len(orders.Data.Orders) < 1 {
				this.UnBuyCount = 0
				this.UnSellCount = 0
				log.Println("暂无委托订单数据")
				continue
			}
			this.HandleUnOrders(symbol, orders.Data.Orders)
		}
	}
}

// 下单
func (this *Trade) CreateOrders(wg *sync.WaitGroup, ctx context.Context, symbol string) {
	defer wg.Done()
	t := time.NewTicker(10 * time.Second)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			log.Println("退出创建订单")
			return
		case <-t.C:
			if this.CurrentPrice <= 0 || this.ATR <= 0 ||
				this.TopPrice <= 0 ||
				this.RecommendSellPrice <= 0 ||
				this.RecommendBuyPrice <= 0 {
				log.Println("[*********等待数据初始化*******]", this.CurrentPrice, this.ATR,
					this.TopPrice,
					this.RecommendSellPrice,
					this.RecommendBuyPrice)
				continue
			}
			if this.BuyPosition.Volume > 0 && this.SellPosition.Volume > 0 {
				log.Println("[**********已有两种持仓********]")
				continue
			}
			this.COrder(symbol)
		}
	}
}

func (this *Trade) COrder(symbol string) {
	log.Println("[***************进行下单策略分析**************]", this.RecommendDirection)
	this.optLock.Lock()
	defer this.optLock.Unlock()
	orderPriceType := "limit"
	direction := "buy"
	offset := "open"
	volume := this.conf.Volume             // 10张
	lever_rate := int(this.conf.LeverRate) // 50倍
	clientOrderID := time.Now().UnixNano()
	// 震荡行情
	if this.RecommendDirection == 0 { // 推荐购买
		if this.UnBuyCount > 0 || this.BuyPosition.Volume > 0 {
			log.Println("委托买单数", this.UnBuyCount)
			// 有正在挂的单
			return
		}
		log.Println("**********进行震荡策略1分析", this.RecommendDirection, this.RecommendBuyPrice)
		orderPriceType = "limit"
		direction = "buy"
		this.UnBuyCount++
		sl_trigger_price := this.RecommendBuyPrice - this.conf.Profit*this.ATR
		// 可以买入
		_, err := this.client.SwapOrder(symbol, symbol, orderPriceType, clientOrderID,
			this.RecommendBuyPrice, volume, direction, offset, lever_rate, sl_trigger_price, sl_trigger_price, "optimal_20")
		if err != nil {
			this.UnBuyCount--
			log.Println("下单失败", err)
			return
		}
		this.LastBuyAt = time.Now().Unix()
		log.Println("[买单市价下单成功]", this.RecommendBuyPrice, volume)
		return
	}
	if this.RecommendDirection == 1 {
		if this.UnSellCount > 0 || this.SellPosition.Volume > 0 {
			log.Println("委托空单数", this.UnSellCount)
			// 有正在挂的单
			return
		}
		log.Println("**********进行震荡策略2分析", this.RecommendDirection, this.RecommendSellPrice)
		direction = "sell"
		orderPriceType = "limit"
		this.UnSellCount++
		sl_trigger_price := this.RecommendSellPrice + this.conf.Profit*this.ATR
		// 可以买入
		_, err := this.client.SwapOrder(symbol, symbol, orderPriceType, clientOrderID,
			this.RecommendSellPrice, volume, direction, offset, lever_rate, sl_trigger_price,
			sl_trigger_price, "optimal_20")
		if err != nil {
			this.UnSellCount--
			log.Println("下单失败", err)
			return
		}
		this.LastSellAt = time.Now().Unix()
		log.Println("[卖单市价下单成功]", this.RecommendSellPrice, volume)
		return
	}
}
